package com.navinfo.platform.trip.common.util;

/**
 * 地图瓦片相关接口封装
 * @author 沈东生
 */
public class TileUtils {
    /**
     * 将long类型的经纬度坐标转为double类型的经纬度坐标
     * @param lngLat long类型的经纬度
     * @return double类型的经纬度
     */
    public static double convertLngLat(long lngLat) {
        return (double) lngLat / Math.pow(10.0D, 6.0D);
    }

    /**
     * 将double类型的经纬度坐标转为long类型的经纬度坐标
     * @param lngLat double类型的经纬度
     * @return long类型的经纬度
     */
    public static long convertToLngLat(double lngLat) {
        return new Double(lngLat * Math.pow(10.0D, 6.0D)).longValue();
    }

    /**
     * 获取地图瓦片编号
     * @param latitude  纬度 以度为单位的纬度值乘以 10 的 6 次方，精确到百万分之一度
     * @param longitude 经度 以度为单位的纬度值乘以 10 的 6 次方，精确到百万分之一度
     * @param zoomLevel 地图缩放级别（瓦片级别） 0-16 有效 15时对应瓦片边长为1千米 16时为500米
     * @return 瓦片编号
     */
    public static long getTileId(long latitude, long longitude, int zoomLevel) {
        double lat = convertLngLat(latitude);
        double lon = convertLngLat(longitude);
        int[] zxy = new int[]{zoomLevel, (int) Math.floor((lon + 180.0D) / 360.0D * (double) (1 << zoomLevel)), (int) Math.floor((1.0D - Math.log(Math.tan(Math.toRadians(lat)) + 1.0D / Math.cos(Math.toRadians(lat))) / 3.141592653589793D) / 2.0D * (double) (1 << zoomLevel))};
        if (zxy[1] < 0) {
            zxy[1] = 0;
        }

        if (zxy[1] >= 1 << zoomLevel) {
            zxy[1] = (1 << zoomLevel) - 1;
        }

        if (zxy[2] < 0) {
            zxy[2] = 0;
        }

        if (zxy[2] >= 1 << zoomLevel) {
            zxy[2] = (1 << zoomLevel) - 1;
        }

        return zxyToTileId(zxy);
    }

    /**
     * 将xyz坐标转为tileId
     * @param zxy zxy坐标
     * @return tileId
     */
    public static long zxyToTileId(int[] zxy) {
        int z = zxy[0];
        int x = zxy[1];
        int y = zxy[2];
        return (long) ((double) z * Math.pow(10.0D, 10.0D)) + (long) ((double) x * Math.pow(10.0D, 5.0D)) + (long) y;
    }


    /**
     * 将tileId转换为zxy坐标
     * @param tileId 瓦片ID
     * @return zxy坐标
     */
    public static int[] tileIdTozxy(long tileId) {
        final int N = (int) Math.pow(10.0D, 5.0D);
        return new int[]{(int) (tileId / N / N), (int) (tileId / N % N), (int) (tileId % N)};
    }

    /*
     * 分辨率，表示水平方向上一个像素点代表的真实距离(m)
     */
    public static double getResolution(long latitude, int level) {
        double dLat = convertLngLat(latitude);
        double a = 6378137.0 * 2 * Math.PI;
        double b = Math.cos(Math.toRadians(dLat));
        return a * b / 256.0 / getMapSize(level);
    }

    /*
     * 某一瓦片等级下瓦片地图X轴(Y轴)上的瓦片数目
     */
    public static int getMapSize(int level) {
        return (int) Math.pow(2.0, 1.0 * level);
    }


    private static int[] calculateParentTileIds(int[] zxy) {
        int z = zxy[0];
        int x = zxy[1];
        int y = zxy[2];

        int x1, y1, z1;
        if (x % 2 == 0) {
            x1 = x / 2;
        } else {
            x1 = (x - 1) / 2;
        }
        if (y % 2 == 0) {
            y1 = y / 2;
        } else {
            y1 = (y - 1) / 2;
        }
        z1 = z - 1;
        return new int[]{z1, x1, y1};
    }

    /**
     * 获取当前tileId上一级的tileId
     * @param tileId 瓦片ID
     * @return 上一级的瓦片ID
     */
    public static long getParentTileId(long tileId) {
        int[] zxy = tileIdTozxy(tileId);
        int[] pZxy = calculateParentTileIds(zxy);
        return zxyToTileId(pZxy);
    }

    /**
     * 每个瓦片图片宽度像素点
     */
    private static final int PIXEL_WIDTH_SIZE = 256;

    /**
     * 瓦片x坐标转为经度
     * @param z      z坐标
     * @param x      x坐标
     * @param pixelX 0-255  像素点坐标
     * @return 经度
     */
    public static long tile2Longitude( int z,int x, int pixelX) {
        return Math.round(((x + pixelX * 1.0 / PIXEL_WIDTH_SIZE) / Math.pow(2.0, z) * 360.0 - 180) * 1000000L);
    }

    /**
     * 瓦片y坐标转为纬度
     * @param z      z坐标
     * @param y      y坐标
     * @param pixelY 0-255 像素点坐标
     * @return 维度
     */
    public static long tile2Latitude(int z, int y, int pixelY) {
        double n = Math.PI - (2.0 * Math.PI * (y + pixelY * 1.0 / PIXEL_WIDTH_SIZE)) / Math.pow(2.0, z);
        return Math.round(Math.toDegrees(Math.atan(Math.sinh(n))) * 1000000L);
    }
}
